/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.security.config.http;

import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

/**
 * @author Luke Taylor
 * @author Ben Alex
 */
class LogoutBeanDefinitionParser implements BeanDefinitionParser {
    static final String ATT_LOGOUT_SUCCESS_URL = "logout-success-url";

    static final String ATT_INVALIDATE_SESSION = "invalidate-session";

    static final String ATT_LOGOUT_URL = "logout-url";
    static final String DEF_LOGOUT_URL = "/logout";
    static final String ATT_LOGOUT_HANDLER = "success-handler-ref";
    static final String ATT_DELETE_COOKIES = "delete-cookies";

    final String rememberMeServices;
    private final String defaultLogoutUrl;
    private ManagedList<BeanMetadataElement> logoutHandlers = new ManagedList<BeanMetadataElement>();
    private boolean csrfEnabled;

    public LogoutBeanDefinitionParser(String loginPageUrl, String rememberMeServices, BeanMetadataElement csrfLogoutHandler) {
        this.defaultLogoutUrl = loginPageUrl + "?logout";
        this.rememberMeServices = rememberMeServices;
        this.csrfEnabled = csrfLogoutHandler != null;
        if (this.csrfEnabled) {
            logoutHandlers.add(csrfLogoutHandler);
        }
    }

    public BeanDefinition parse(Element element, ParserContext pc) {
        String logoutUrl = null;
        String successHandlerRef = null;
        String logoutSuccessUrl = null;
        String invalidateSession = null;
        String deleteCookies = null;

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class);

        if (element != null) {
            Object source = pc.extractSource(element);
            builder.getRawBeanDefinition().setSource(source);
            logoutUrl = element.getAttribute(ATT_LOGOUT_URL);
            successHandlerRef = element.getAttribute(ATT_LOGOUT_HANDLER);
            WebConfigUtils.validateHttpRedirect(logoutUrl, pc, source);
            logoutSuccessUrl = element.getAttribute(ATT_LOGOUT_SUCCESS_URL);
            WebConfigUtils.validateHttpRedirect(logoutSuccessUrl, pc, source);
            invalidateSession = element.getAttribute(ATT_INVALIDATE_SESSION);
            deleteCookies = element.getAttribute(ATT_DELETE_COOKIES);
        }

        if (!StringUtils.hasText(logoutUrl)) {
            logoutUrl = DEF_LOGOUT_URL;
        }


        builder.addPropertyValue("logoutRequestMatcher", getLogoutRequestMatcher(logoutUrl));

        if (StringUtils.hasText(successHandlerRef)) {
            if (StringUtils.hasText(logoutSuccessUrl)) {
                pc.getReaderContext().error("Use " + ATT_LOGOUT_SUCCESS_URL + " or " + ATT_LOGOUT_HANDLER + ", but not both",
                        pc.extractSource(element));
            }
            builder.addConstructorArgReference(successHandlerRef);
        } else {
            // Use the logout URL if no handler set
            if (!StringUtils.hasText(logoutSuccessUrl)) {
                logoutSuccessUrl = defaultLogoutUrl;
            }
            builder.addConstructorArgValue(logoutSuccessUrl);
        }

        BeanDefinition sclh = new RootBeanDefinition(SecurityContextLogoutHandler.class);
        sclh.getPropertyValues().addPropertyValue("invalidateHttpSession", !"false".equals(invalidateSession));
        logoutHandlers.add(sclh);

        if (rememberMeServices != null) {
            logoutHandlers.add(new RuntimeBeanReference(rememberMeServices));
        }

        if (StringUtils.hasText(deleteCookies)) {
            BeanDefinition cookieDeleter = new RootBeanDefinition(CookieClearingLogoutHandler.class);
            String[] names = StringUtils.tokenizeToStringArray(deleteCookies, ",");
            cookieDeleter.getConstructorArgumentValues().addGenericArgumentValue(names);
            logoutHandlers.add(cookieDeleter);
        }

        builder.addConstructorArgValue(logoutHandlers);

        return builder.getBeanDefinition();
    }

    private BeanDefinition getLogoutRequestMatcher(String logoutUrl) {
        BeanDefinitionBuilder matcherBuilder = BeanDefinitionBuilder.rootBeanDefinition("org.springframework.security.web.util.matcher.AntPathRequestMatcher");
        matcherBuilder.addConstructorArgValue(logoutUrl);
        if(this.csrfEnabled) {
            matcherBuilder.addConstructorArgValue("POST");
        }

        return matcherBuilder.getBeanDefinition();
    }

    ManagedList<BeanMetadataElement> getLogoutHandlers() {
        return logoutHandlers;
    }
}
